)
* data-speed="1" Animation speed multiplier (0.1–3)
* data-density="28" Number of rising particles (4–80)
* data-controls="false" Hide the on-page Tweaks panel (default: shown)
*
* The illustration scales to fill its container while preserving its 1:1
* aspect ratio. Give the container an explicit width and height (or
* aspect-ratio) — the widget does the rest. Renders inside a Shadow DOM, so
* it cannot conflict with Webflow's site-wide CSS.
*/
(function(){
function mount(host){
if (host.__koreMounted) return;
host.__koreMounted = true;
const root = host.attachShadow({mode:'open'});
root.innerHTML = "
\n
\n\n \n
\n\n \n
\n
\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n
\n\n \n
\n\n
\n
\n\n
\n
Tweaks \n
\n Speed 1.00× \n \n
\n
\n Density 28 \n \n
\n
Higher density = more rising elements.
\n
";
// Re-implement getElementById against the shadow root.
const $ = (id) => root.getElementById(id);
const stage = $('stage');
// Fit the 777.6 design canvas to the host element's box.
function fit(){
const W = 777.6, H = 777.6;
const rect = host.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const s = Math.min(rect.width / W, rect.height / H);
const offX = (rect.width - W*s) / 2;
const offY = (rect.height - H*s) / 2;
stage.style.transformOrigin = '0 0';
stage.style.left = offX + 'px';
stage.style.top = offY + 'px';
stage.style.transform = 'scale(' + s + ')';
}
requestAnimationFrame(fit);
new ResizeObserver(fit).observe(host);
window.addEventListener('load', fit);
// === Particle animation (adapted from the original inline script) ===
const COLORS = [
'rgb(50,199,202)','rgb(83,241,246)','rgb(117,244,248)',
'rgb(176,249,251)','rgb(6,97,111)','rgb(3,120,136)','rgb(55,199,202)'
];
const colA = $('colA'), colB = $('colB');
const LANES_A = [4,28,56,86,118,144];
const LANES_B = [10,36,64,92,120,150];
const COL_A_H = 459.807, COL_B_H = 465.377;
const state = { speed:1, density:28, particles:[], lastT: performance.now() };
const rand = (a,b) => a + Math.random()*(b-a);
const pick = a => a[Math.floor(Math.random()*a.length)];
function spawn(){
const useA = Math.random() < 0.5;
const parent = useA ? colA : colB;
const lanes = useA ? LANES_A : LANES_B;
const colH = useA ? COL_A_H : COL_B_H;
const lane = pick(lanes);
const height = rand(14,70);
const duration = rand(2.4,5.6);
const startDelay = rand(0,duration);
const el = document.createElement('div');
el.className = 'particle';
el.style.left = lane + 'px';
el.style.height = height + 'px';
el.style.background = pick(COLORS);
parent.appendChild(el);
return { el, column: useA?'A':'B', colH, height, duration, progress: -startDelay/duration };
}
function setDensity(n){
state.density = n;
while (state.particles.length < n) state.particles.push(spawn());
while (state.particles.length > n) state.particles.pop().el.remove();
}
function recycle(p){
const useA = Math.random() < 0.5;
const parent = useA ? colA : colB;
const lanes = useA ? LANES_A : LANES_B;
const colH = useA ? COL_A_H : COL_B_H;
if (p.column !== (useA?'A':'B')) { parent.appendChild(p.el); p.column = useA?'A':'B'; }
p.colH = colH;
p.height = rand(14,70);
p.duration = rand(2.4,5.6);
p.progress = 0;
p.el.style.left = pick(lanes) + 'px';
p.el.style.height = p.height + 'px';
p.el.style.background = pick(COLORS);
}
function tick(now){
const dt = Math.min(0.05, (now - state.lastT)/1000);
state.lastT = now;
for (const p of state.particles){
p.progress += (dt * state.speed) / p.duration;
if (p.progress >= 1){ recycle(p); continue; }
if (p.progress < 0){ p.el.style.visibility='hidden'; continue; }
p.el.style.visibility='visible';
const t = p.progress;
const y = p.colH - t * (p.colH + p.height);
let fade = 1;
if (t < 0.08) fade = t / 0.08;
else if (t > 0.85) fade = (1 - t) / 0.15;
p.el.style.transform = 'translateY(' + y.toFixed(2) + 'px)';
p.el.style.opacity = (fade * 0.95).toFixed(2);
}
requestAnimationFrame(tick);
}
// === Tweaks panel wiring ===
const speedEl = $('speed'), speedV = $('speedV');
const densityEl = $('density'), densityV = $('densityV');
speedEl.addEventListener('input', () => {
state.speed = parseFloat(speedEl.value);
speedV.textContent = state.speed.toFixed(2) + '×';
});
densityEl.addEventListener('input', () => {
const n = parseInt(densityEl.value, 10);
densityV.textContent = n;
setDensity(n);
});
// Read optional config from data-attrs
const initSpeed = parseFloat(host.getAttribute('data-speed') || '1');
const initDensity = parseInt(host.getAttribute('data-density') || '28', 10);
const showPanel = host.getAttribute('data-controls') !== 'false';
if (!showPanel) {
const panel = root.querySelector('.panel');
if (panel) panel.style.display = 'none';
}
speedEl.value = initSpeed; state.speed = initSpeed;
speedV.textContent = initSpeed.toFixed(2) + '×';
densityEl.value = initDensity;
densityV.textContent = initDensity;
setDensity(initDensity);
requestAnimationFrame(t => { state.lastT = t; tick(t); });
}
function init(){
const targets = document.querySelectorAll('[data-kore-illustration]');
targets.forEach(mount);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// Expose a manual mount in case markup is added dynamically.
window.KoreIllustration = { mount, init };
})();